#include "xmiwriter.h"

XMIWriter::XMIWriter()
{
    this->_isWMI = true;
    this->_filePath = "";
}

void XMIWriter::setIsWMI(bool isWMI)
{
    this->_isWMI = isWMI;
}

void XMIWriter::run()
{
    QString filePath = this->_filePath.length() == 0 ? this->_project->path() : this->_filePath;
    QFile file(filePath);
    file.open(QIODevice::WriteOnly);

    // <xmi:XMI ...></xmi:XMI>
    this->_doc.appendChild(this->xmiRootTag());

    // <?xml version="1.0" encoding="UTF-8"?>
    QDomNode xml = this->_doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
    this->_doc.insertBefore(xml, this->_doc.firstChild());

    QTextStream textStream(&file);
    this->_doc.save(textStream, 4);

    file.close();
}

QDomElement XMIWriter::xmiRootTag()
{
    QDomElement xmi = this->_doc.createElement("xmi:XMI");
    xmi.setAttribute("xmi:version", "2.1");
    xmi.setAttribute("xmlns:xmi", "http://schema.omg.org/spec/XMI/2.1");
    xmi.setAttribute("xmlns:uml", "http://schema.omg.org/spec/UML/2.1");
    xmi.appendChild(this->documentationTag());
    xmi.appendChild(this->umlModelTag());

    return xmi;
}

QDomElement XMIWriter::documentationTag()
{
    QDomElement documentation = this->_doc.createElement("xmi:Documentation");
    documentation.setAttribute("exporter", "WUml");
    documentation.setAttribute("exporterVersion", "0.1");

    return documentation;
}

QDomElement XMIWriter::umlModelTag()
{
    QDomElement model = this->_doc.createElement("uml:Model");
    model.setAttribute("xmi:type", "uml:Model");
    model.setAttribute("xmi:id", this->_project->id());
    model.setAttribute("name", "Model:" + this->_project->name());

    QDomElement rootDir = this->_doc.createElement("packagedElement");
    rootDir.setAttribute("xmi:type", "uml:Package");
    rootDir.setAttribute("xmi:id", QUuid::createUuid());
    rootDir.setAttribute("name", this->_project->name());

    model.appendChild(rootDir);

    foreach (UmlGraphicsItem *item, this->_project->umlItemRoot()->items()) {
        this->packagedElement(item, &rootDir);
    }

    // writing associations
    foreach(QString id, this->_associations.keys()) {
        this->packagedElement(this->_associations[id], &rootDir);
        this->_associations.remove(id);
    }

    return model;
}

void XMIWriter::packagedElement(UmlGraphicsItem *item, QDomElement *parent)
{
    if (item->isOfType(UmlItem::UmlPackageItemType)) {
        UmlDirItem *dirItem = static_cast<UmlDirItem*>(item);

        QDomElement dir = this->_doc.createElement("packagedElement");
        dir.setAttribute("xmi:type", "uml:Package");
        dir.setAttribute("xmi:id", dirItem->id());
        dir.setAttribute("name", dirItem->name());
        parent->appendChild(dir);

        QListIterator<UmlGraphicsItem*> iter(dirItem->items());
        while (iter.hasNext()) {
            this->packagedElement(iter.next(), &dir);
        }

        // writing associations
        foreach(QString id, this->_associations.keys()) {
            this->packagedElement(this->_associations[id], parent);
            this->_associations.remove(id);
        }

    }

    if (item->isOfType(UmlItem::UmlClassItemType)) {
        UmlClassItem *classItem = static_cast<UmlClassItem*>(item);

        QDomElement tag = this->_doc.createElement("packagedElement");
        tag.setAttribute("xmi:id", classItem->id());
        tag.setAttribute("visibility", "package");

        if (this->_isWMI) {
            tag.setAttribute("x_pos", classItem->pos().x());
            tag.setAttribute("y_pos", classItem->pos().y());
            tag.setAttribute("show_atts", classItem->showAtts());
            tag.setAttribute("show_meths", classItem->showMeths() );
        }

        if (classItem->isInterface()) {
            tag.setAttribute("xmi:type", "uml:Interface");

            // writing realization relations
            QList<UmlRelation*> interfaces = classItem->parentClasses();
            QListIterator<UmlRelation*> iter (interfaces);
            while (iter.hasNext()) {
                UmlRelation *relation = iter.next();
                QDomElement realization = this->_doc.createElement("packagedElement");
                realization.setAttribute("xmi:type", "uml:Realization");
                realization.setAttribute("xmi:id", relation->id());
                realization.setAttribute("client", relation->item1()->id());
                realization.setAttribute("supplier", relation->item2()->id());
                realization.setAttribute("realizingClassifier", relation->item2()->id());
                parent->appendChild(realization);
            }

        } else {
            tag.setAttribute("xmi:type", "uml:Class");

            foreach (UmlRelation *relation, classItem->parentClasses()) {
                QDomElement inheritance = this->_doc.createElement("generalization");
                inheritance.setAttribute("xmi:type", "uml:Generalization");
                inheritance.setAttribute("xmi:id", relation->id());
                inheritance.setAttribute("general", relation->item2()->id());
                tag.appendChild(inheritance);
            }

            this->_realizations.append(classItem->realizedInterfaces());
        }
        tag.setAttribute("xmi:id", classItem->id());
        tag.setAttribute("name", classItem->name());
        parent->appendChild(tag);

        // writing realizations
        foreach (UmlRelation *relation, classItem->realizedInterfaces()) {
            QDomElement realization = this->_doc.createElement("packagedElement");
            realization.setAttribute("xmi:type", "uml:Realization");
            realization.setAttribute("xmi:id", relation->id());
            realization.setAttribute("client", relation->item1()->id());
            realization.setAttribute("supplier", relation->item2()->id());
            realization.setAttribute("realizingClassfier", relation->item2()->id());
            parent->appendChild(realization);
        }

        // collecting associtations
        foreach (UmlRelation *relation, classItem->allAssociations()) {
            this->_associations[relation->id()] = relation;
        }

        this->ownedAttribute(item, &tag);

    }

    else if (item->isOfType(UmlItem::UmlRelationType)) {
        UmlRelation *relation = dynamic_cast<UmlRelation*>(item);

        QDomElement packagedElement = this->_doc.createElement("packagedElement");
        packagedElement.setAttribute("xmi:type", "uml:Association");
        packagedElement.setAttribute("xmi:id",relation->id());
        packagedElement.setAttribute("name", relation->name());
        packagedElement.setAttribute("visibility", "private");

        QDomElement memberEnd1 = this->_doc.createElement("memberEnd");
        memberEnd1.setAttribute("xmi:idref", "{prop}" + relation->item1()->id());

        QDomElement memberEnd2 = this->_doc.createElement("memberEnd");
        memberEnd2.setAttribute("xmi:idref", "{prop}" + relation->item2()->id());

        packagedElement.appendChild(memberEnd1);
        packagedElement.appendChild(memberEnd2);

        parent->appendChild(packagedElement);
    }
}

void XMIWriter::ownedAttribute(UmlGraphicsItem *item, QDomElement *parent)
{
    if (item->isOfType(UmlItem::UmlClassItemType)) {
        UmlClassItem *classItem = static_cast<UmlClassItem*>(item);

        // writing attributes
        QListIterator<ClassAttribute*> iter1 (classItem->attList());
        while (iter1.hasNext()) {
            ClassAttribute *att = iter1.next();
            QDomElement ownedAttribute = this->_doc.createElement("ownedAttribute");
            ownedAttribute.setAttribute("xmi:type", "uml:Property");
            ownedAttribute.setAttribute("xmi:id", att->id());
            ownedAttribute.setAttribute("name", att->name());
            ownedAttribute.setAttribute("visibility", att->visibilityToString());
            parent->appendChild(ownedAttribute);

            // data type
            QDomElement type = this->_doc.createElement("type");
            XmiDatatype dataType = this->_dataTypes->dataTypeByLabel(att->returnType());
            if (dataType.isPrimitive()) {
                type.setAttribute("xmi:type", "uml:PrimitiveType");
                type.setAttribute("href", dataType.id());
            }
            ownedAttribute.appendChild(type);
        }

        // writing associations
        foreach (UmlRelation *relation, classItem->allAssociations()) {

            QString id = relation->item1()->id() == classItem->id() ? relation->item2()->id() : relation->item1()->id();

            QDomElement ownedAttribute = this->_doc.createElement("ownedAttribute");
            ownedAttribute.setAttribute("xmi:id", "{prop}" + id);
            ownedAttribute.setAttribute("xmi:type", "uml:Property");
            ownedAttribute.setAttribute("name", "");
            ownedAttribute.setAttribute("visibility", "private");
            ownedAttribute.setAttribute("association", relation->id());

            if (relation->relationIsOfType(UmlRelation::UmlAggregationRelationType) &&
                classItem == relation->item2()) {
                ownedAttribute.setAttribute("aggregation", "shared");
            }

            else if (relation->relationIsOfType(UmlRelation::UmlCompositionRelationType) &&
                     classItem == relation->item2()) {
                     ownedAttribute.setAttribute("aggregation", "composite");
            }

            else {
                ownedAttribute.setAttribute("aggregation", "none");
            }


            QDomElement type = this->_doc.createElement("type");
            type.setAttribute("xmi:type", "uml:Class");
            type.setAttribute("xmi:idref", id);

            ownedAttribute.appendChild(type);
            parent->appendChild(ownedAttribute);


            QDomElement lowerValue = this->_doc.createElement("lowerValue");
            QDomElement upperValue = this->_doc.createElement("upperValue");

            lowerValue.setAttribute("xmi:type", "uml:literalString");
            upperValue.setAttribute("xmi:type", "uml:literalString");

            if (relation->item1() == classItem) {
                lowerValue.setAttribute("xmi:id", "{multiplicity}" + relation->item2()->id());
                lowerValue.setAttribute("value", relation->cardinalityItem1().lowerBound());
                upperValue.setAttribute("xmi:id", "{multiplicity}" + relation->item2()->id());
                upperValue.setAttribute("value", relation->cardinalityItem2().upperBound());
            }
            else {
                lowerValue.setAttribute("xmi:id", "{multiplicity}" + relation->item1()->id());
                lowerValue.setAttribute("value", relation->cardinalityItem1().lowerBound());
                upperValue.setAttribute("xmi:id", "{multiplicity}" + relation->item1()->id());
                upperValue.setAttribute("value", relation->cardinalityItem1().upperBound());
            }

            ownedAttribute.appendChild(lowerValue);
            ownedAttribute.appendChild(upperValue);

        }

        // writing operations
        QListIterator<ClassMethod*> iter2 (classItem->methList());
        while (iter2.hasNext()) {
            ClassMethod *meth = iter2.next();
            QDomElement ownedOperation = this->_doc.createElement("ownedOperation");
            ownedOperation.setAttribute("xmi:type", "uml:Operation");
            ownedOperation.setAttribute("xmi:id", meth->id());
            ownedOperation.setAttribute("name", meth->name());
            ownedOperation.setAttribute("visibility", meth->visibilityToString());
            parent->appendChild(ownedOperation);

            // return type
            QDomElement ownedParameter = this->_doc.createElement("ownedParameter");
            ownedParameter.setAttribute("name", "return");
            ownedParameter.setAttribute("direction", "return");
            ownedParameter.setAttribute("id", meth->id());
            ownedOperation.appendChild(ownedParameter);

            XmiDatatype dataType1 = this->_dataTypes->dataTypeByLabel(meth->returnType());
            QDomElement type = this->_doc.createElement("type");
            if (dataType1.isPrimitive()) {
                type.setAttribute("xmi:type", "uml:PrimitiveType");
            } else {
                type.setAttribute("xmi:type", "uml:Class");
            }
            type.setAttribute("href", dataType1.id());
            ownedParameter.appendChild(type);

            // method's parameters
            QList<ClassAttribute*> params = meth->argList();
            QListIterator<ClassAttribute*> iter3(params);
            while (iter3.hasNext()) {
                ClassAttribute *param = iter3.next();
                QDomElement ownedParameter = this->_doc.createElement("ownedParameter");
                ownedParameter.setAttribute("xmi:type", "uml:Parameter");
                ownedParameter.setAttribute("name", param->name());
                ownedParameter.setAttribute("xmi:id", param->id());
                ownedParameter.setAttribute("direction", "in");
                ownedOperation.appendChild(ownedParameter);


                QDomElement type = this->_doc.createElement("type");
                XmiDatatype dataType2 = this->_dataTypes->dataTypeByLabel(param->returnType());
                if (dataType2.isPrimitive()) {
                    type.setAttribute("xmi:type", "uml:PrimitiveType");
                    type.setAttribute("href", dataType2.id());
                }

                ownedParameter.appendChild(type);
            }
        }
    }
}
